/*
 * File     : internal_NativeUnsafeAccess.c
 * Created  : 28 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "common.h"
#include <string.h>

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    allocateMemory
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_allocateMemory(
    JNIEnv* env,
    jclass clazz,
    jlong size )
{
    void* ptr = malloc( (size_t) size );
    if ( ptr != NULL )
    {
        return POINTER_TO_JLONG( ptr );
    }

    (*env)->ThrowNew( env, (*env)->FindClass( env, "java/lang/OutOfMemoryError" ), "Native memory" );
    return 0;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    copyMemory
 * Signature: (JJJ)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_copyMemory(
    JNIEnv* env,
    jclass clazz,
    jlong sourcePointer,
    jlong destPointer,
    jlong size )
{
    memmove( JLONG_TO_POINTER( destPointer ),
             JLONG_TO_POINTER( sourcePointer ),
             (size_t) size );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    freeMemory
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_freeMemory(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    free( JLONG_TO_POINTER( pointer ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getBoolean
 * Signature: (J)Z
 */
JNIEXPORT jboolean JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getBoolean(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
 	jint value = *( (jint*) JLONG_TO_POINTER( pointer ) );
    return value != 0 ? JNI_TRUE : JNI_FALSE;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getByte
 * Signature: (J)B
 */
JNIEXPORT jbyte JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getByte(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    return *( (jbyte*) JLONG_TO_POINTER( pointer ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getChar
 * Signature: (J)C
 */
JNIEXPORT jchar JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getChar(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    return *( (jchar*) JLONG_TO_POINTER( pointer ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getDouble
 * Signature: (J)D
 */
JNIEXPORT jdouble JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getDouble(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    return *( (jdouble*) JLONG_TO_POINTER( pointer ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getFloat
 * Signature: (J)F
 */
JNIEXPORT jfloat JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getFloat(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    return *( (jfloat*) JLONG_TO_POINTER( pointer ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getInt
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getInt(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    return *( (jint*) JLONG_TO_POINTER( pointer ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getLong
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getLong(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    return *( (jlong*) JLONG_TO_POINTER( pointer ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getShort
 * Signature: (J)S
 */
JNIEXPORT jshort JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getShort(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    return *( (jshort*) JLONG_TO_POINTER( pointer ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getPointer
 * Signature: (J)J
 */
JNIEXPORT jlong JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getPointer(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    return POINTER_TO_JLONG( *( (void**) JLONG_TO_POINTER( pointer ) ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    putBoolean
 * Signature: (JZ)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_putBoolean(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jboolean value )
{
    *( (jint*) JLONG_TO_POINTER( pointer ) ) = value == JNI_TRUE ? 1 : 0;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    putByte
 * Signature: (JB)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_putByte(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jbyte value )
{
    *( (jbyte*) JLONG_TO_POINTER( pointer ) ) = value;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    putChar
 * Signature: (JC)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_putChar(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jchar value )
{
    *( (jchar*) JLONG_TO_POINTER( pointer ) ) = value;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    putDouble
 * Signature: (JD)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_putDouble(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jdouble value )
{
    *( (jdouble*) JLONG_TO_POINTER( pointer ) ) = value;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    putFloat
 * Signature: (JF)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_putFloat(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jfloat value )
{
    *( (jfloat*) JLONG_TO_POINTER( pointer ) ) = value;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    putInt
 * Signature: (JI)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_putInt(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jint value )
{
    *( (jint*) JLONG_TO_POINTER( pointer ) ) = value;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    putLong
 * Signature: (JJ)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_putLong(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jlong value )
{
    *( (jlong*) JLONG_TO_POINTER( pointer ) ) = value;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    putShort
 * Signature: (JS)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_putShort(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jshort value )
{
    *( (jshort*) JLONG_TO_POINTER( pointer ) ) = value;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    setMemory
 * Signature: (JJB)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_setMemory(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jlong size,
    jbyte value )
{
    memset( JLONG_TO_POINTER( pointer ), (int) value, (size_t) size );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    copyFromArray
 * Signature: (J[BI)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_copyFromArray(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jbyteArray array,
    jint size )
{
    void* source;

    source = (*env)->GetPrimitiveArrayCritical( env, array, NULL );
    if ( source == NULL )
    {
        return;
    }

    memcpy( JLONG_TO_POINTER( pointer ), source, (size_t) size );

    (*env)->ReleasePrimitiveArrayCritical( env, array, source, JNI_ABORT );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getStringUTFChars
 * Signature: (Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getStringUTFChars(
    JNIEnv* env,
    jclass clazz,
    jstring string )
{
    return POINTER_TO_JLONG( (*env)->GetStringUTFChars( env, string, NULL ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    releaseStringUTFChars
 * Signature: (Ljava/lang/String;J)V
 */
JNIEXPORT void JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_releaseStringUTFChars(
    JNIEnv* env,
    jclass clazz,
    jstring string,
    jlong pointer )
{
    (*env)->ReleaseStringUTFChars( env, string, JLONG_TO_POINTER( pointer ) );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    defineClass
 * Signature: (Ljava/lang/String;Ljava/lang/ClassLoader;[BI;)Ljava/lang/Class;
 */
JNIEXPORT jclass JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_defineClass(
    JNIEnv* env,
    jclass clazz,
    jstring name,
    jobject classLoader,
    jbyteArray buffer,
    jint bufferLength )
{
    jbyte* bufferCopy;
    jclass definedClazz;
    const char* nameStr;

    bufferCopy = malloc( bufferLength );
    if ( bufferCopy == NULL )
    {
        (*env)->ThrowNew( env, (*env)->FindClass( env, "java/lang/OutOfMemoryError" ), "malloc" );
        return NULL;
    }

    nameStr = (*env)->GetStringUTFChars( env, name, NULL );
    if ( nameStr == NULL )
    {
        free( bufferCopy );
        return NULL;
    }

    (*env)->GetByteArrayRegion( env, buffer, 0, bufferLength, bufferCopy );

    definedClazz = (*env)->DefineClass( env, nameStr, classLoader, bufferCopy, bufferLength );

    (*env)->ReleaseStringUTFChars( env, name, nameStr );
    free( bufferCopy );

    return definedClazz;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getBuffer
 * Signature: (JI)Ljava/nio/ByteBuffer;
 */
JNIEXPORT jobject JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getBuffer(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jint size )
{
    // if this returns NULL, an exception has already been thrown
    return (*env)->NewDirectByteBuffer( env, JLONG_TO_POINTER( pointer ), (jlong) size );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    getBufferPointer
 * Signature: (Ljava/nio/Buffer;)J
 */
JNIEXPORT jlong JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_getBufferPointer(
    JNIEnv* env,
    jclass clazz,
    jobject buffer )
{
    // if this returns NULL, an exception has already been thrown
    void* ptr = (*env)->GetDirectBufferAddress( env, buffer );
    return POINTER_TO_JLONG( ptr );
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    writeUtf8String
 * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_writeUtf8String(
    JNIEnv* env,
    jclass clazz,
    jstring string,
    jobject buffer,
    jint offset )
{
    char* ptr;
    char* basePtr;
    const jchar* array;
    jint length;
    int i;

    ptr = (char*) (*env)->GetDirectBufferAddress( env, buffer );
    if ( ptr == NULL )
    {
        // if this returns NULL, an exception has already been thrown
        return 0;
    }
    ptr += offset;
    basePtr = ptr;

    length = (*env)->GetStringLength( env, string );

    array = (*env)->GetStringCritical( env, string, NULL );
    if ( array == NULL )
    {
        // if this returns NULL, an exception has already been thrown
        return 0;
    }

    for ( i = 0; i < length; i++ )
    {
        jchar c = array[ i ];

        if ( c < 0x0080 )
        {
            *(ptr++) = c;
        }
        else if ( c < 0x800 )
        {
            *(ptr++) = ( 0xc0 | ( ( c >> 6 ) & 0x1f ) );
            *(ptr++) = ( 0x80 | ( c & 0x3f ) );
        }
        else if ( ( c & 0xfc00 ) == 0xd800 )
        {
            int v;
            jchar next;

            // high surrogate; next should be low surrogate
            if ( i == length - 1 )
            {
                break;
            }
            next = array[ i ];
            if ( ( next & 0xfc00 ) != 0xdc00 )
            {
                break;
            }

            v = 0x10000 + ( ( next & 0x3ff ) | ( ( c & 0x3ff ) << 10 ) );

            *(ptr++) = ( 0xf0 | ( ( v >> 18 ) & 0x07 ) );
            *(ptr++) = ( 0x80 | ( ( v >> 12 ) & 0x3f ) );
            *(ptr++) = ( 0x80 | ( ( v >> 6 ) & 0x3f ) );
            *(ptr++) = ( 0x80 | ( v & 0x3f ) );

            i++;
        }
        else
        {
            *(ptr++) = ( 0xe0 | ( ( c >> 12 ) & 0x0f ) );
            *(ptr++) = ( 0x80 | ( ( c >> 6 ) & 0x3f ) );
            *(ptr++) = ( 0x80 | ( c & 0x3f ) );
        }
    }

    (*env)->ReleaseStringCritical( env, string, array );

    *(ptr++) = 0;

    return ptr - basePtr;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    writeIso88591String
 * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_writeIso88591String(
    JNIEnv* env,
    jclass clazz,
    jstring string,
    jobject buffer,
    jint offset )
{
    char* ptr;
    const jchar* array;
    jint length;
    int i;

    ptr = (char*) (*env)->GetDirectBufferAddress( env, buffer );
    if ( ptr == NULL )
    {
        // if this returns NULL, an exception has already been thrown
        return 0;
    }
    ptr += offset;

    length = (*env)->GetStringLength( env, string );

    array = (*env)->GetStringCritical( env, string, NULL );
    if ( array == NULL )
    {
        // if this returns NULL, an exception has already been thrown
        return 0;
    }

    for ( i = 0; i < length; i++ )
    {
        jchar c = array[ i ];
        *(ptr++) = c < 0x0100 ? c : '?';
    }

    (*env)->ReleaseStringCritical( env, string, array );

    *(ptr++) = 0;

    return length + 1;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    writeAsciiString
 * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_writeAsciiString(
    JNIEnv* env,
    jclass clazz,
    jstring string,
    jobject buffer,
    jint offset )
{
    char* ptr;
    const jchar* array;
    jint length;
    int i;

    ptr = (char*) (*env)->GetDirectBufferAddress( env, buffer );
    if ( ptr == NULL )
    {
        // if this returns NULL, an exception has already been thrown
        return 0;
    }
    ptr += offset;

    length = (*env)->GetStringLength( env, string );

    array = (*env)->GetStringCritical( env, string, NULL );
    if ( array == NULL )
    {
        // if this returns NULL, an exception has already been thrown
        return 0;
    }

    for ( i = 0; i < length; i++ )
    {
        jchar c = array[ i ];
        *(ptr++) = c < 0x80 ? c : '?';
    }

    (*env)->ReleaseStringCritical( env, string, array );

    *(ptr++) = 0;

    return length + 1;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    writeUtf16String
 * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_writeUtf16String(
    JNIEnv* env,
    jclass clazz,
    jstring string,
    jobject buffer,
    jint offset )
{
    jchar* ptr;
    jint length;

    ptr = (jchar*) (*env)->GetDirectBufferAddress( env, buffer );
    if ( ptr == NULL )
    {
        // if this returns NULL, an exception has already been thrown
        return 0;
    }
    ptr += offset;

    length = (*env)->GetStringLength( env, string );

    (*env)->GetStringRegion( env, string, 0, length, ptr );

    *(ptr + length) = 0;

    return length * 2 + 2;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    writeLookupString
 * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;IJI)I
 */
JNIEXPORT jint JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_writeLookupString(
    JNIEnv* env,
    jclass clazz,
    jstring string,
    jobject buffer,
    jint offset,
    jlong lookupPointer,
    jint lookupSize )
{
    char* ptr;
    char* lookupTable = JLONG_TO_POINTER( lookupPointer );
    const jchar* array;
    jint length;
    int i;

    ptr = (char*) (*env)->GetDirectBufferAddress( env, buffer );
    if ( ptr == NULL )
    {
        // if this returns NULL, an exception has already been thrown
        return 0;
    }
    ptr += offset;

    length = (*env)->GetStringLength( env, string );

    array = (*env)->GetStringCritical( env, string, NULL );
    if ( array == NULL )
    {
        // if this returns NULL, an exception has already been thrown
        return 0;
    }

    for ( i = 0; i < length; i++ )
    {
        jchar c = array[ i ];
        *(ptr++) = c < lookupSize ? lookupTable[ c ] : '?';
    }

    (*env)->ReleaseStringCritical( env, string, array );

    *(ptr++) = 0;

    return length + 1;
}

static int decodeUtf8( const char* src, jchar* dest, int maximum )
{
    int i = 0;
    unsigned char c;

    while ( i < maximum )
    {
        c = (unsigned char) *(src++);
        if ( c == 0 )
        {
            return i;
        }

        if ( c < 0x80 )
        {
             dest[ i++ ] = c;
        }
        else if ( ( c & 0xe0 ) == 0xc0 )
        {
            char d = *(src++);
            if ( ( d & 0xc0 ) != 0x80 )
            {
                // malformed string
                return i;
            }

            dest[ i++ ] = ( ( c & 0x1f ) << 6 ) | ( d & 0x3f );
        }
        else if ( ( c & 0xf0 ) == 0xe0 )
        {
            char d = *(src++);
            char e = *(src++);
            if ( ( d & 0xc0 ) != 0x80 || ( e & 0xc0 ) != 0x80 )
            {
                // malformed string
                return i;
            }

            dest[ i++ ] = ( ( c & 0x0f ) << 12 ) | ( ( d & 0x3f ) << 6 ) | ( e & 0x3f );
        }
        else
        {
            char d = *(src++);
            char e = *(src++);
            char f = *(src++);
            int codePoint;

            if ( ( d & 0xc0 ) != 0x80 || ( e & 0xc0 ) != 0x80 || ( f & 0xc0 ) != 0x80 )
            {
                // malformed string
                return i;
            }

            codePoint = ( ( c & 0x0f ) << 18 ) | ( ( d & 0x3f ) << 12 ) | ( ( e & 0x3f ) << 6 ) | ( f & 0x3f );
            codePoint += 0x10000;

            dest[ i++ ] = 0xd800 | ( codePoint >> 10 );
            dest[ i++ ] = 0xdc00 | ( codePoint & 0x3ff );
        }
    }

    c = *(src++);
    if ( c == 0 )
    {
        return i;
    }

    // the string wasn't terminated; give up
    return -1;
}

// TODO not convinced by my try on the stack first approach...

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    readUtf8String
 * Signature: (J)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_readUtf8String(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    jchar temp[ 1024 ];
    int i;
    int srcSize;
    char* src;
    jchar* dest;
    jstring result;

    src = (char*) JLONG_TO_POINTER( pointer );

    // first of all, try with a small, stack-allocated buffer
    // (leave space for two jchars since a UTF-8 symbol may produce two jchars)
    dest = temp;
    i = decodeUtf8( src, dest, 1023 );

    if ( i >= 0 )
    {
        return (*env)->NewString( env, dest, i );
    }

    // didn't fit in the stack-allocate buffer;
    // count how long the string is in bytes
    srcSize = strlen( src );

    // there's an upper bound of 1 UTF-8 byte for a UTF-16 symbol
    dest = (jchar*) malloc( 2 * srcSize );

    if ( dest == NULL )
    {
        (*env)->ThrowNew( env, (*env)->FindClass( env, "java/lang/OutOfMemoryError" ), "Native memory" );
        return 0;
    }

    i = decodeUtf8( src, dest, srcSize );

    if ( i < 0 )
    {
        (*env)->ThrowNew( env, (*env)->FindClass( env, "java/lang/AssertionError" ), "Oops" );
        return 0;
    }

    result = (*env)->NewString( env, dest, i );
    free( dest );

    return result;
}

static int decodeIso88591( const char* src, jchar* dest, int maximum )
{
    int i = 0;
    unsigned char c;

    while ( i < maximum )
    {
        c = (unsigned char) *(src++);
        if ( c == 0 )
        {
            return i;
        }

        dest[ i++ ] = c;
    }

    c = *(src++);
    if ( c == 0 )
    {
        return i;
    }

    // the string wasn't terminated; give up
    return -1;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    readIso88591String
 * Signature: (JJ)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_readIso88591String(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    jchar temp[ 1024 ];
    int i;
    int srcSize;
    char* src;
    jchar* dest;
    jstring result;

    src = (char*) JLONG_TO_POINTER( pointer );

    // first of all, try with a small, stack-allocated buffer
    dest = temp;
    i = decodeIso88591( src, dest, 1024 );

    if ( i >= 0 )
    {
        return (*env)->NewString( env, dest, i );
    }

    // didn't fit in the stack-allocate buffer;
    // count how long the string is in bytes
    srcSize = strlen( src );

    // there's an upper bound of 1 byte for a UTF-16 symbol
    dest = (jchar*) malloc( 2 * srcSize );

    if ( dest == NULL )
    {
        (*env)->ThrowNew( env, (*env)->FindClass( env, "java/lang/OutOfMemoryError" ), "Native memory" );
        return 0;
    }

    i = decodeIso88591( src, dest, 1024 );

    if ( i < 0 )
    {
        (*env)->ThrowNew( env, (*env)->FindClass( env, "java/lang/AssertionError" ), "Oops" );
        return 0;
    }

    result = (*env)->NewString( env, dest, i );
    free( dest );

    return result;
}


/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    readUtf16String
 * Signature: (J)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_readUtf16String(
    JNIEnv* env,
    jclass clazz,
    jlong pointer )
{
    int srcSize;
    jchar* src;

    src = (jchar*) JLONG_TO_POINTER( pointer );

    // find the end zero word
    // TODO I suspect there's a 'nice' x86 instruction that does this for us...
    srcSize = 0;
    while ( src[ srcSize ] != 0 )
    {
        srcSize++;
    }

    return (*env)->NewString( env, src, srcSize );
}

static int decodeLookup( const char* src, jchar* dest, int maximum, jchar* lookupTable )
{
    int i = 0;
    unsigned char c;

    while ( i < maximum )
    {
        c = (unsigned char) *(src++);
        if ( c == 0 )
        {
            return i;
        }

        dest[ i++ ] = lookupTable[ c ];
    }

    c = *(src++);
    if ( c == 0 )
    {
        return i;
    }

    // the string wasn't terminated; give up
    return -1;
}

/*
 * Class:     com_googlecode_dni_internal_NativeUnsafeAccess
 * Method:    readLookupString
 * Signature: (JJ)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_googlecode_dni_internal_NativeUnsafeAccess_readLookupString(
    JNIEnv* env,
    jclass clazz,
    jlong pointer,
    jlong lookupPointer )
{
    jchar temp[ 1024 ];
    int i;
    int srcSize;
    char* src;
    jchar* lookupTable;
    jchar* dest;
    jstring result;

    src = (char*) JLONG_TO_POINTER( pointer );
    lookupTable = (jchar*) JLONG_TO_POINTER( pointer );

    // first of all, try with a small, stack-allocated buffer
    dest = temp;
    i = decodeLookup( src, dest, 1024, lookupTable );

    if ( i >= 0 )
    {
        return (*env)->NewString( env, dest, i );
    }

    // didn't fit in the stack-allocate buffer;
    // count how long the string is in bytes
    srcSize = strlen( src );

    // there's an upper bound of 1 byte for a UTF-16 symbol
    dest = (jchar*) malloc( 2 * srcSize );

    if ( dest == NULL )
    {
        (*env)->ThrowNew( env, (*env)->FindClass( env, "java/lang/OutOfMemoryError" ), "Native memory" );
        return 0;
    }

    i = decodeLookup( src, dest, srcSize, lookupTable );

    if ( i < 0 )
    {
        (*env)->ThrowNew( env, (*env)->FindClass( env, "java/lang/AssertionError" ), "Oops" );
        return 0;
    }

    result = (*env)->NewString( env, dest, i );
    free( dest );

    return result;
}

